package net.stuffrepos.bricknetcenter.util.gui;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import javax.swing.JPanel;
import net.stuffrepos.util.cache.CacheableMapValue;
import net.stuffrepos.bricknetcenter.util.HierarchicalProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public class GapedPanel extends JPanel {

    public static enum ConstraintProperty {

        GridWidth,
        GridHeight,
        GridX,
        GridY,
        Fill,
        WidthX,
        WidthY
    }
    private static final Log log = LogFactory.getLog(GapedPanel.class);
    private final CacheableMapValue<Integer, Row> rows = new CacheableMapValue<Integer, Row>() {

        @Override
        protected Row calculate(Integer key) {
            return new Row(key);
        }
    };
    private final GridBagLayout gridBagLayout = new GridBagLayout();
    private CacheableMapValue<Integer, Column> columns = new CacheableMapValue<Integer, Column>() {

        @Override
        protected Column calculate(Integer key) {
            return new Column(key);
        }
    };

    public GapedPanel() {
        this.setLayout(gridBagLayout);
        constraintProperties.setValue(ConstraintProperty.GridWidth, 1);
        constraintProperties.setValue(ConstraintProperty.GridHeight, 1);
        constraintProperties.setValue(ConstraintProperty.GridX, GridBagConstraints.RELATIVE);
        constraintProperties.setValue(ConstraintProperty.GridY, GridBagConstraints.RELATIVE);
        constraintProperties.setValue(ConstraintProperty.Fill, GridBagConstraints.BOTH);
        constraintProperties.setValue(ConstraintProperty.WidthX, 0.0);
        constraintProperties.setValue(ConstraintProperty.WidthY, 0.0);
    }
    private HierarchicalProperties constraintProperties =
            new HierarchicalProperties();

    public GapedPanel setConstraintProperty(ConstraintProperty constraintProperty, double value) {
        constraintProperties.setValue(constraintProperty, value);
        return this;
    }

    @Override
    public Component add(Component component) {
        return add(
                component,
                constraintProperties.getIntegerValue(ConstraintProperty.GridWidth),
                constraintProperties.getIntegerValue(ConstraintProperty.GridHeight));
    }

    public Component add(Component component, int gridWidth, int gridHeigth) {
        return add(
                component,
                gridWidth,
                gridHeigth,
                constraintProperties.getIntegerValue(ConstraintProperty.GridX),
                constraintProperties.getIntegerValue(ConstraintProperty.GridY));
    }

    public Component add(Component component, int gridWidth, int gridHeight, int gridX, int gridY) {
        return this.add(
                component,
                gridWidth,
                gridHeight,
                gridX,
                gridY,
                constraintProperties.getIntegerValue(ConstraintProperty.Fill));
    }

    public Component add(Component component, int gridWidth, int gridHeigth, int gridX, int gridY, int fill) {
        return add(
                component,
                gridWidth,
                gridHeigth,
                gridX,
                gridY,
                fill,
                constraintProperties.getDoubleValue(ConstraintProperty.WidthX),
                constraintProperties.getDoubleValue(ConstraintProperty.WidthY));
    }

    public Component add(Component component, int gridWidth, int gridHeigth, int gridX, int gridY, int fill, double widthX, double widthY) {
        assert component != null;
        if (log.isTraceEnabled()) {
            log.trace(
                    String.format(
                    "Adding %s(gw:%d,gh:%d,gx:%d,gy:%d,fi:%d,wx:%.1f,wy:%.1f)",
                    component.getClass().getSimpleName(),
                    gridWidth,
                    gridHeigth,
                    gridX,
                    gridY,
                    fill,
                    widthX,
                    widthY));
        }
        GridBagConstraints gridBagConstraints = new GridBagConstraints();
        gridBagLayout.setConstraints(component, gridBagConstraints);
        gridBagConstraints.fill = fill;
        gridBagConstraints.weightx = widthX;
        gridBagConstraints.weighty = widthY;
        gridBagConstraints.gridwidth = gridWidth;
        gridBagConstraints.gridheight = gridHeigth;
        gridBagConstraints.gridx = gridX;
        gridBagConstraints.gridy = gridY;
        gridBagConstraints.insets = new Insets(3, 3, 3, 3);
        gridBagLayout.setConstraints(component, gridBagConstraints);
        return super.add(component);
    }

    public Row getRow(int gridY) {
        return rows.getValue(gridY);
    }

    public Column getColumn(int gridX) {
        return columns.getValue(gridX);
    }

    public class Coordinate<SelfType extends Coordinate> {

        protected final HierarchicalProperties constraintProperties;
        private final int coordinate;

        public Coordinate(int coordinate, HierarchicalProperties parentConstraintProperties) {
            this.coordinate = coordinate;
            this.constraintProperties = new HierarchicalProperties(parentConstraintProperties);
        }

        public int getCoordinate() {
            return coordinate;
        }

        public SelfType setConstraintProperty(ConstraintProperty constraintProperty, double value) {
            this.constraintProperties.setValue(constraintProperty, value);
            return (SelfType) this;
        }

        public int getConstraintProperty(ConstraintProperty constraintProperty) {
            return this.constraintProperties.getIntegerValue(constraintProperty);
        }
    }

    public abstract class MainCoordinate<SelfType extends MainCoordinate> extends Coordinate<SelfType> {

        private CacheableMapValue<Integer, SubCoordinate> columns = new CacheableMapValue<Integer, SubCoordinate>() {

            @Override
            protected SubCoordinate calculate(Integer key) {
                return (SubCoordinate) new SubCoordinate(key, MainCoordinate.this.constraintProperties);
            }
        };

        private MainCoordinate(int key) {
            super(key, GapedPanel.this.constraintProperties);
        }

        public SubCoordinate getSub(int gridX) {
            return columns.getValue(gridX);
        }

        public abstract Point getGridXY(SubCoordinate subCoordinate);

        public class SubCoordinate extends Coordinate<SubCoordinate> {

            private SubCoordinate(int key, HierarchicalProperties parentConstraintProperties) {
                super(key, parentConstraintProperties);
            }

            public MainCoordinate add(Component component) {
                return this.add(component, getConstraintProperty(ConstraintProperty.GridWidth));
            }

            public MainCoordinate add(Component component, int gridWidth) {
                return this.add(component, gridWidth, getConstraintProperty(ConstraintProperty.GridHeight));
            }

            public MainCoordinate add(Component component, int gridWidth, int gridHeigth) {
                return this.add(component, gridWidth, gridHeigth, getConstraintProperty(ConstraintProperty.Fill));
            }

            public MainCoordinate add(Component component, int gridWidth, int gridHeigth, int fill) {
                return add(
                        component,
                        gridWidth,
                        gridHeigth,
                        fill,
                        getConstraintProperty(ConstraintProperty.WidthX),
                        getConstraintProperty(ConstraintProperty.WidthY));
            }

            public MainCoordinate add(Component component, int gridWidth, int gridHeigth, int fill, double widthX, double widthY) {
                Point gridXY = getGridXY(this);
                GapedPanel.this.add(component, gridWidth, gridHeigth, gridXY.x, gridXY.y, fill, widthX, widthY);
                return MainCoordinate.this;
            }
        }
    }

    public class Row extends MainCoordinate<Row> {

        private Row(int key) {
            super(key);
        }

        @Override
        public Point getGridXY(SubCoordinate subCoordinate) {
            return new Point(subCoordinate.getCoordinate(), this.getCoordinate());
        }
    }

    public class Column extends MainCoordinate<Column> {

        private Column(int key) {
            super(key);
        }

        @Override
        public Point getGridXY(SubCoordinate subCoordinate) {
            return new Point(this.getCoordinate(), subCoordinate.getCoordinate());
        }
    }
}
